package com.chrray.position.service.auth.impl;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.chrray.position.auth.Authentication;
import com.chrray.position.auth.AuthenticationContext;
import com.chrray.position.config.AuthServerProperties;
import com.chrray.position.domain.FdAuthEnterprise;
import com.chrray.position.domain.FdAuthPersonal;
import com.chrray.position.domain.FdUser;
import com.chrray.position.domain.dto.FdAuthEnterpriseDTO;
import com.chrray.position.domain.dto.FdAuthPersonalDTO;
import com.chrray.position.enums.YesNoEnum;
import com.chrray.position.enums.app.FdGenderEnum;
import com.chrray.position.enums.app.FdUserRoleEnum;
import com.chrray.position.exception.ServiceException;
import com.chrray.position.pojo.vo.auth.WxLoginVo;
import com.chrray.position.pojo.vo.util.UploadVo;
import com.chrray.position.service.auth.WxAuthService;
import com.chrray.position.service.upload.UploadrService;
import com.chrray.position.util.md5.Md5Utils;
import com.chrray.position.util.redis.RedisUtils;
import com.chrray.position.util.result.ResponseResult;
import com.chrray.position.util.result.ResultEnum;
import com.chrray.position.util.wx.WXutil;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.http.*;
import org.springframework.http.client.MultipartBodyBuilder;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.StringHttpMessageConverter;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.MultiValueMap;
import org.springframework.util.StringUtils;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.multipart.MultipartFile;
import tk.mybatis.mapper.util.Assert;

import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.time.LocalDateTime;
import java.util.Date;
import java.util.List;

import static com.chrray.position.util.result.ResponseResults.fail;
import static com.chrray.position.util.result.ResponseResults.success;

@Slf4j
@Service
@AllArgsConstructor
public class WxAuthServiceImpl implements WxAuthService {
    private final AuthServerProperties authServerProperties;
    private final  RedisUtils redisUtils;
    private final UploadrService uploadrService;

    private static final RestTemplate restTemplate = new RestTemplate();
    static {
        List<HttpMessageConverter<?>> messageConverters = restTemplate.getMessageConverters();
        for (HttpMessageConverter<?> item : messageConverters) {
            if(item instanceof StringHttpMessageConverter) {
                ((StringHttpMessageConverter) item).setDefaultCharset(StandardCharsets.UTF_8);
            }
        }
    }
    private static final String wxcode2session = "https://api.weixin.qq.com/sns/jscode2session?appid=%s&secret=%s&js_code=%s&grant_type=authorization_code";


    /**
     * -1 inner error
     * 101001	certificate not found
     * 101002	decode image failed
     * 101003	not enough market quota
     * 40001	invalid credential  access_token isinvalid or not latest
     */
    private String decodeErr(int code) {
        if(code == -1) {
            return "服务暂时不可用";
        } else if(code == 101001) {
            return "图片中未检测到证件";
        } else if(code == 101002) {
            return "图片识别失败";
        } else if(code == 101003) {
            return "识别次数已达上限";
        }else if (code == 40001) {
            return "access_token无效或过期";
        }
        return "识别失败";
    }

    /**
     * 企业实名认证
     * https://developers.weixin.qq.com/miniprogram/dev/OpenApiDoc/img-ocr/ocr/businessLicenseOCR.html
     */
    @SneakyThrows
    @Override
    public ResponseResult<FdAuthEnterpriseDTO> entRealNameAuth(MultipartFile idImg) {
        String accessToken = getAccessToken();
        String body = null;
        String active = System.getProperty("spring.profiles.active");
        if("dev".equals(active)) {
            body = "{\"errcode\":0,\"errmsg\":\"ok\",\"reg_num\":\"123123\",\"serial\":\"123123\",\"legal_representative\":\"张三\",\"enterprise_name\":\"XX饮食店\",\"type_of_organization\":\"个人经营\",\"address\":\"XX市XX区XX路XX号\",\"type_of_enterprise\":\"xxx\",\"business_scope\":\"中型餐馆(不含凉菜、不含裱花蛋糕，不含生食海产品)。\",\"registered_capital\":\"200万\",\"paid_in_capital\":\"200万\",\"valid_period\":\"2019年1月1日\",\"registered_date\":\"2018年1月1日\",\"cert_position\":{\"pos\":{\"left_top\":{\"x\":155,\"y\":191},\"right_top\":{\"x\":725,\"y\":157},\"right_bottom\":{\"x\":743,\"y\":512},\"left_bottom\":{\"x\":164,\"y\":525}}},\"img_size\":{\"w\":966,\"h\":728}}";
        } else {
            String url = "https://api.weixin.qq.com/cv/ocr/bizlicense?access_token=" + accessToken;
            HttpHeaders headers = new HttpHeaders();
            headers.setContentType(MediaType.MULTIPART_FORM_DATA);
            MultipartBodyBuilder builder = new MultipartBodyBuilder();
            builder.part("img", idImg.getResource());
            HttpEntity<MultiValueMap<String, HttpEntity<?>>> entity = new HttpEntity<>(builder.build(), headers);
            ResponseEntity<String> response = restTemplate.exchange(url, HttpMethod.POST, entity, String.class);
            body = response.getBody();
        }


        JSONObject authJSON = JSON.parseObject(body);
        log.info("[bizlicense auth] authJSON:{}",authJSON);

        Integer errcode = authJSON.getInteger("errcode");
        if(errcode != 0) {
            throw new ServiceException(decodeErr(errcode));
        }

        UploadVo uploadVo = (UploadVo) uploadrService.uploadPic(idImg).getData();

        FdAuthEnterpriseDTO enterprise = new FdAuthEnterpriseDTO();
        enterprise.setCertImg(uploadVo.getFullPathUrl());
        enterprise.setEnterpriseName(authJSON.getString("enterprise_name"));
        // type_of_enterprise
        String type = authJSON.getString("type_of_organization");
        if(StringUtils.hasLength(type)) {
            enterprise.setTypeOfOrganization(type);
        } else {
            enterprise.setTypeOfOrganization(authJSON.getString("type_of_enterprise"));
        }
        enterprise.setLegalRepresentative(authJSON.getString("legal_representative"));
        enterprise.setRegNum(authJSON.getString("reg_num"));
        enterprise.setSerial(authJSON.getString("serial"));
        enterprise.setRegisteredDate(authJSON.getString("registered_date"));
        enterprise.setValidPeriod(authJSON.getString("valid_period"));

        return success(enterprise);

    }


    @Override
    public ResponseResult<String> saveEnterpriseAuth(FdAuthEnterpriseDTO body) {
        FdAuthEnterprise enterprise = new FdAuthEnterprise().findOneByUserId(AuthenticationContext.context().getUserId());
        if(enterprise == null) {
            enterprise = new FdAuthEnterprise();
            BeanUtils.copyProperties(body,enterprise);

            enterprise.setId(null);
            enterprise.setUserId(AuthenticationContext.context().getUserId());
            enterprise.setDelFlag(YesNoEnum.NO.getCode());
            enterprise.setCreateTime(new Date());
            enterprise.insert();

        } else {
            enterprise.setCertImg(body.getCertImg());
            enterprise.setEnterpriseName(body.getEnterpriseName());
            enterprise.setRegNum(body.getRegNum());
            enterprise.setSerial(body.getSerial());
            enterprise.setLegalRepresentative(body.getLegalRepresentative());
            enterprise.setTypeOfOrganization(body.getTypeOfOrganization());
            enterprise.setRegisteredDate(body.getRegisteredDate());
            enterprise.setValidPeriod(body.getValidPeriod());

            enterprise.updateById();
        }


        FdUser currentUser = FdUser.getCurrentUser();
        currentUser.setRealNamed(YesNoEnum.YES.getCode());
        currentUser.setEntRealNamed(YesNoEnum.YES.getCode());
        currentUser.setUpdateTime(new Date());
        currentUser.updateById();

        Authentication context = AuthenticationContext.context();
        context.setRealNamed(YesNoEnum.YES.getCode());
        context.setEntRealNamed(YesNoEnum.YES.getCode());
        AuthenticationContext.refreshContext(context);
        return success("认证成功");
    }

    /**
     * 切换登录身份
     */
    @Override
    public ResponseResult<Authentication> changeRole(Integer roleV) {
        FdUserRoleEnum role = FdUserRoleEnum.getByValue(roleV);
        Assert.notNull(role,"role is null");
        Authentication context = AuthenticationContext.context();
        Integer currentRole = context.getCurrentRole();
        if (currentRole != role.getValue()) {
            context.setCurrentRole(role.getValue());
            AuthenticationContext.refreshContext(context);
            // 持久化到数据库
            FdUser currentUser = FdUser.getCurrentUser();
            currentUser.setCurrentRole(role.getValue());
            currentUser.setUpdateTime(new Date());
            currentUser.updateById();
        }
        return success(context);
    }



    /**
     * 个人实名认证
     * https://api.weixin.qq.com/cv/ocr/idcard?access_token=ACCESS_TOKEN
     */
    @SneakyThrows
    @Override
    @Transactional(rollbackFor = Exception.class)
    public ResponseResult<FdAuthPersonalDTO> personRealNameAuth(MultipartFile idImg) {

        String accessToken = getAccessToken();
        String body = null;
        String active = System.getProperty("spring.profiles.active");
        if("dev".equals(active)) {
            body = "\n" +
                    "{\n" +
                    "  \"errcode\": \"0\",\n" +
                    "  \"errmsg\": \"ok\",\n" +
                    "  \"type\": \"Front\",\n" +
                    "  \"name\": \"张三\",\n" +
                    "  \"id\": \"123456789012345678\",\n" +
                    "  \"addr\": \"广东省广州市\",\n" +
                    "  \"gender\": \"男\",\n" +
                    "  \"nationality\": \"汉\"\n" +
                    "}";
        } else {
            String url = "https://api.weixin.qq.com/cv/ocr/idcard?access_token=" + accessToken;
            HttpHeaders headers = new HttpHeaders();
            headers.setContentType(MediaType.MULTIPART_FORM_DATA);
            MultipartBodyBuilder builder = new MultipartBodyBuilder();
            builder.part("img", idImg.getResource());
            HttpEntity<MultiValueMap<String, HttpEntity<?>>> entity = new HttpEntity<>(builder.build(), headers);

            ResponseEntity<String> response = restTemplate.exchange(url, HttpMethod.POST, entity, String.class);
            log.info("[res header] {}", response.getHeaders());
            body = response.getBody();
        }


        /**
         * {
         *   "errcode": "0",
         *   "errmsg": "ok",
         *   "type": "Front",
         *   "name": "张三",
         *   "id": "123456789012345678",
         *   "addr": "广东省广州市",
         *   "gender": "男",
         *   "nationality": "汉"
         * }
         */
        JSONObject authJSON = JSON.parseObject(body);
        log.info("[idcard auth] authJSON:{}",authJSON);

        Integer errcode = authJSON.getInteger("errcode");
        if(errcode != 0) {
            throw new ServiceException(decodeErr(errcode));
        }

        // 上传图片
        UploadVo uploadVo = (UploadVo) uploadrService.uploadPic(idImg).getData();


        FdAuthPersonalDTO dto = new FdAuthPersonalDTO();
        dto.setName(authJSON.getString("name"));
        dto.setIdNo(authJSON.getString("id"));
        String genderStr = authJSON.getString("gender");
        if("男".equals(genderStr)) {
            dto.setGender(FdGenderEnum.MALE.getValue());
        } else {
            dto.setGender(FdGenderEnum.FEMALE.getValue());
        }
        dto.setAddr(authJSON.getString("addr"));
        dto.setBirthYear(Integer.parseInt(authJSON.getString("id").substring(6,10)));
        dto.setFaceImg(uploadVo.getFullPathUrl());

        return success(dto);
    }


    @Override
    public ResponseResult<String> savePersonalAuth(FdAuthPersonalDTO body) {
        FdAuthPersonal personal = new FdAuthPersonal().findOneByUserId(AuthenticationContext.context().getUserId());
        if(personal == null) {
            personal = new FdAuthPersonal();
            BeanUtils.copyProperties(body,personal);

            personal.setId(null);
            personal.setUserId(AuthenticationContext.context().getUserId());
            personal.setDelFlag(YesNoEnum.NO.getCode());
            personal.setCreateTime(new Date());
            personal.insert();

        } else {
            personal.setFaceImg(body.getFaceImg());
            personal.setName(body.getName());
            personal.setIdNo(body.getIdNo());
            personal.setGender(body.getGender());
            personal.setAddr(body.getAddr());
            personal.setBirthYear(body.getBirthYear());

            personal.updateById();
        }

        FdUser currentUser = FdUser.getCurrentUser();
        currentUser.setRealNamed(YesNoEnum.YES.getCode());
        currentUser.setPersonalRealNamed(YesNoEnum.YES.getCode());
        currentUser.setUpdateTime(new Date());
        currentUser.updateById();

        Authentication context = AuthenticationContext.context();
        context.setRealNamed(YesNoEnum.YES.getCode());
        context.setPersonalRealNamed(YesNoEnum.YES.getCode());
        AuthenticationContext.refreshContext(context);
        return success("认证成功");
    }




    @Override
    public ResponseResult<WxLoginVo> jscodeAuth(String code) {
        String active = System.getProperty("spring.profiles.active");
        String openId = null;
        if("local".equals(active)) {
            openId = "123456";
        } else {
            String url = String.format(wxcode2session, authServerProperties.getAppid(), authServerProperties.getAppsecret(), code);
            ResponseEntity<String> responseEntity = restTemplate.getForEntity(url, String.class);
            String resStr = responseEntity.getBody();
            if(StringUtils.isEmpty(resStr)) {
                return fail(ResultEnum.LOGIN_USER_WARN);
            }
            WxReponse body = JSON.parseObject(responseEntity.getBody(), WxReponse.class);
            if(body.errcode != 0) {
                return fail(ResultEnum.LOGIN_USER_WARN);
            }
            openId = body.getOpenid();
        }
        Assert.notNull(openId,"openId is null");


        FdUser user = new FdUser().findUserByWxId(openId);
        if (user == null) {
            // 新增小程序用户
            user = FdUser.createWxTourist(openId);
        }
        if (user.getState() == YesNoEnum.YES.getCode()) {
            return fail(ResultEnum.LOGIN_USER_WARN);
        }
        String token = "wx_"+Md5Utils.MD5(LocalDateTime.now().toString() + user.getId());


        Authentication context = new Authentication();
        context.setToken(token);
        context.setUserId(user.getId());
        context.setWxId(openId);
        context.setCurrentRole(user.getCurrentRole());
        context.setRealNamed(user.getRealNamed());
        context.setEntRealNamed(user.getEntRealNamed());
        context.setPersonalRealNamed(user.getPersonalRealNamed());
        AuthenticationContext.storeContext(context);


        return success(new WxLoginVo(token,user.getCurrentRole(),user.getRealNamed(),user.getEntRealNamed(),user.getPersonalRealNamed()));
    }


    /**
     * 获取微信access_token
     */
    private String getAccessToken() throws IOException {
        String token = redisUtils.getString("wx_access_token");
        if(StringUtils.isEmpty(token)) {
            synchronized (this) {
                token = redisUtils.getString("wx_access_token");
                if(!StringUtils.isEmpty(token)) {
                    return token;
                }
                String active = System.getProperty("spring.profiles.active");
                if("dev1111".equals(active)) {
                    return "123456";
                }
                String res = WXutil.accessToken(authServerProperties.getAppid(), authServerProperties.getAppsecret());
                JSONObject objJson = JSON.parseObject(res);
                String accessToken = objJson.getString("access_token");
                Integer expiresIn = objJson.getInteger("expires_in");
                redisUtils.setHour("wx_access_token",accessToken,expiresIn/3600);
                log.info("[wx access_token] got new access_token:{} ",accessToken);
                return accessToken;
            }
        }
        return token;
    }




    @Data
    public static class WxReponse {
        private int errcode;
        private String errmsg;
        private String openid;
        private String sessionKey;
        private String unionid;
    }
}
